/*******************************************************************\
| PhotonMap.h
|
| CREATED:		5.6.2009
| DESCRIPTION:
| 
| Photon map class
| Encapsulates all functionallity to create and use the photon map
\********************************************************************/
#ifndef PHOTONMAP_H
#define PHOTONMAP_H

#include "Scene.h"
#include "RayTraceRecorder.h"

namespace PhotonMapping
{

// pointer typedef
class PhotonMap;
typedef std::tr1::shared_ptr<PhotonMap> PhotonMapPtr;


//*****************************************************************************
// class overview:
//   
//-----------------------------------------------------------------------------
class PhotonMap
{
	public:
		// photon structure
		struct Photon
		{
			Math::Vector3 m_Position;	// photon hit position
			byte m_Power[4];			// photon power (RGBE packed)
			byte m_Phi, m_Theta;		// incident angles
			short m_Flags;				// flags used depending on photon map implementation
		};

		enum Technique
		{
			SWKdTree = 0,		// software kd-tree from Jensen
			GPUKdTree = 1,		// GPU kd-tree, Zhou
			GPUHash = 2,		// GPU Spatial Hashing
			GPUBF = 3			// GPU BruteForce from Garcia
		};

	public:
		PhotonMap(float p_MaxQueryRadius, uintx p_MaxQueryPhotons, uintx p_Technique);
		~PhotonMap();

		// create the photon map, numphotons specifies the number of photons emitted per light source
		// if caustic map is true photons will be shot only towards specular/refractive objects to create
		// a caustic map
		void Create(ScenePtr p_Scene, uintx p_NumPhotons, bool p_CausticMap);

		// prepare photon map for photon query (building kd-tree or other structures - depending on
		// the used technique
		void Prepare();

		// radiance estimate for the given point
		Color RadianceEstimate(const Math::Vector3& p_Point);

		// stores the photon map to the given file
		static bool Store(const tchar* p_FileName, PhotonMapPtr p_Map);

		// loads the photon map from the given file
		static PhotonMapPtr Load(const tchar* p_FileName, float p_MaxDist, uintx p_MaxPhotons, uintx p_Technique);

		// returns the photon map (used to visualize photon map in debugging)
		const std::vector<Photon>& GetPhotons() const { return m_PhotonMap; }

		// deferrred estimation
		void DeferredEstimate(RayTraceRecorderPtr p_Recorder);

	private:
		// photon tracing function
		void PhotonTrace(ScenePtr p_Scene, const Math::Ray& p_Ray, const Color& p_Power, uintx p_DiffBounces, uintx p_SpecBounces, bool p_CausticMap);

		// traces a shadow photon
		void ShadowPhotonTrace(ScenePtr p_Scene, const Math::Ray& p_Ray, const Color& p_Power);

		// add a photon to the photon map
		void AddPhoton(const Math::Vector3& p_Position, const Math::Vector3& p_Dir, const Color& p_Power);

		// helper functions to build the software kd-tree
		void BuildKdTree();

		// balances kd-tree segment
		void BalanceSegment(std::vector<Photon*>& p_Bal, std::vector<Photon*>& p_Org, const int p_Index, const int p_Start, const int p_End);

		// splits the given segment at the specified median position
		void MedianSplit(std::vector<Photon*>& p_Photons, const int p_Start, const int p_End, const int p_Median, const int p_Axis);

		// return the directin for the given spherical coordinates
		const Math::Vector3 GetPhotonDir(const byte p_Theta, const byte p_Phi);

		// deferred estimation using BF method
		void DeferredEstimateBF(RayTraceRecorderPtr p_Recorder);

		// deferred estimation using GPU kd-tree method
		void DeferredEstimateKdTree(RayTraceRecorderPtr p_Recorder);

		// deferred estimation using GPU hash method
		void DeferredEstimateHash(RayTraceRecorderPtr p_Recorder);

		// searches the kd-tree for photons
		struct LocateContext
		{
			LocateContext(const Math::Vector3& p_Pos, const uintx p_MaxPhotons, const float p_MaxSqDist) 
				: m_Pos(p_Pos), m_Photons(p_MaxPhotons + 1), m_SqDistances(p_MaxPhotons + 1),
				  m_PhotonsFound(0), m_BuildHeap(1), m_MaxPhotons(p_MaxPhotons)
			{ 
				m_SqDistances[0] = p_MaxSqDist;
			}

			Math::Vector3 m_Pos;
			uintx m_PhotonsFound;
			uintx m_BuildHeap;
			uintx m_MaxPhotons;
			std::vector<const Photon*> m_Photons;
			std::vector<float> m_SqDistances;
		};
		void LocatePhotons(LocateContext& p_Context, const uintx p_Index);

		// photon map
		std::vector<Photon>	m_PhotonMap;

		// photon bounding box
		Math::Vector3 m_BBMin, m_BBMax;

		// half number of photons
		uintx m_NumPhotons;
		uintx m_NumPhotonsHalf;

		// angle conversion tables
		float m_CosTheta[256];
		float m_SinTheta[256];
		float m_CosPhi[256];
		float m_SinPhi[256];

		float m_Exposure;

		// maximum query radius
		float m_QueryRadius;

		// maximum number of queried photons
		uintx m_MaxPhotons;

		// handle to the GPU kd-tree
		void* m_KdTree;

		// handle to GPU hash map
		void* m_HashMap;

		// used photon map technique
		const uintx m_Technique;

		// cuda timers
		unsigned int m_ConstructionTimer, m_EstimateTimer, m_PhotonGatherTimer;

		// maximum number of traces for photon tracing
		static const intx MaxTraces = 100;

		// knn-BF technique query width
		static const int m_kNNBFQueryWidth;
};

}

#endif // PHOTONMAP_H
